// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/renderer/screen_orientation/screen_orientation_dispatcher.h"

#include <list>
#include <memory>
#include <tuple>

#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "content/public/test/render_view_test.h"
#include "content/public/test/test_utils.h"
#include "third_party/WebKit/public/platform/modules/screen_orientation/WebLockOrientationCallback.h"

namespace content {

using LockOrientationCallback = device::mojom::ScreenOrientation::LockOrientationCallback;
using LockResult = device::mojom::ScreenOrientationLockResult;

// MockLockOrientationCallback is an implementation of
// WebLockOrientationCallback and takes a LockOrientationResultHolder* as a
// parameter when being constructed. The |results_| pointer is owned by the
// caller and not by the callback object. The intent being that as soon as the
// callback is resolved, it will be killed so we use the
// LockOrientationResultHolder to know in which state the callback object is at
// any time.
class MockLockOrientationCallback : public blink::WebLockOrientationCallback {
public:
    struct LockOrientationResultHolder {
        LockOrientationResultHolder()
            : succeeded_(false)
            , failed_(false)
        {
        }

        bool succeeded_;
        bool failed_;
        blink::WebLockOrientationError error_;
    };

    explicit MockLockOrientationCallback(LockOrientationResultHolder* results)
        : results_(results)
    {
    }

    void onSuccess() override { results_->succeeded_ = true; }

    void onError(blink::WebLockOrientationError error) override
    {
        results_->failed_ = true;
        results_->error_ = error;
    }

private:
    LockOrientationResultHolder* results_;
};

// TODO(lunalu): When available, test mojo service without needing a
// RenderViewTest.
class ScreenOrientationDispatcherTest : public RenderViewTest {
protected:
    void SetUp() override
    {
        RenderViewTest::SetUp();
        dispatcher_.reset(new ScreenOrientationDispatcher(nullptr));
        ScreenOrientationAssociatedPtr screen_orientation;
        mojo::GetIsolatedProxy(&screen_orientation);
        dispatcher_->SetScreenOrientationForTests(screen_orientation);
    }

    void LockOrientation(
        blink::WebScreenOrientationLockType orientation,
        std::unique_ptr<blink::WebLockOrientationCallback> callback)
    {
        dispatcher_->lockOrientation(orientation, std::move(callback));
    }

    void UnlockOrientation() { dispatcher_->unlockOrientation(); }

    int GetRequestId() { return dispatcher_->GetRequestIdForTests(); }

    void RunLockResultCallback(int request_id, LockResult result)
    {
        dispatcher_->OnLockOrientationResult(request_id, result);
    }

    std::unique_ptr<ScreenOrientationDispatcher> dispatcher_;
};

// Test that calling lockOrientation() followed by unlockOrientation() cancel
// the lockOrientation().
TEST_F(ScreenOrientationDispatcherTest, CancelPending_Unlocking)
{
    MockLockOrientationCallback::LockOrientationResultHolder callback_results;

    LockOrientation(
        blink::WebScreenOrientationLockPortraitPrimary,
        base::MakeUnique<MockLockOrientationCallback>(&callback_results));
    UnlockOrientation();

    EXPECT_FALSE(callback_results.succeeded_);
    EXPECT_TRUE(callback_results.failed_);
    EXPECT_EQ(blink::WebLockOrientationErrorCanceled, callback_results.error_);
}

// Test that calling lockOrientation() twice cancel the first lockOrientation().
TEST_F(ScreenOrientationDispatcherTest, CancelPending_DoubleLock)
{
    MockLockOrientationCallback::LockOrientationResultHolder callback_results;
    // We create the object to prevent leaks but never actually use it.
    MockLockOrientationCallback::LockOrientationResultHolder callback_results2;

    LockOrientation(
        blink::WebScreenOrientationLockPortraitPrimary,
        base::MakeUnique<MockLockOrientationCallback>(&callback_results));

    LockOrientation(
        blink::WebScreenOrientationLockPortraitPrimary,
        base::MakeUnique<MockLockOrientationCallback>(&callback_results2));

    EXPECT_FALSE(callback_results.succeeded_);
    EXPECT_TRUE(callback_results.failed_);
    EXPECT_EQ(blink::WebLockOrientationErrorCanceled, callback_results.error_);
}

// Test that when a LockError message is received, the request is set as failed
// with the correct values.
TEST_F(ScreenOrientationDispatcherTest, LockRequest_Error)
{
    std::map<LockResult, blink::WebLockOrientationError> errors;
    errors[LockResult::SCREEN_ORIENTATION_LOCK_RESULT_ERROR_NOT_AVAILABLE] = blink::WebLockOrientationErrorNotAvailable;
    errors[LockResult::SCREEN_ORIENTATION_LOCK_RESULT_ERROR_FULLSCREEN_REQUIRED] = blink::WebLockOrientationErrorFullscreenRequired;
    errors[LockResult::SCREEN_ORIENTATION_LOCK_RESULT_ERROR_CANCELED] = blink::WebLockOrientationErrorCanceled;

    for (std::map<LockResult, blink::WebLockOrientationError>::const_iterator it = errors.begin();
         it != errors.end(); ++it) {
        MockLockOrientationCallback::LockOrientationResultHolder callback_results;
        LockOrientation(
            blink::WebScreenOrientationLockPortraitPrimary,
            base::MakeUnique<MockLockOrientationCallback>(&callback_results));
        RunLockResultCallback(GetRequestId(), it->first);
        EXPECT_FALSE(callback_results.succeeded_);
        EXPECT_TRUE(callback_results.failed_);
        EXPECT_EQ(it->second, callback_results.error_);
    }
}

// Test that when a LockSuccess message is received, the request is set as
// succeeded.
TEST_F(ScreenOrientationDispatcherTest, LockRequest_Success)
{
    MockLockOrientationCallback::LockOrientationResultHolder callback_results;
    LockOrientation(
        blink::WebScreenOrientationLockPortraitPrimary,
        base::MakeUnique<MockLockOrientationCallback>(&callback_results));

    RunLockResultCallback(GetRequestId(),
        LockResult::SCREEN_ORIENTATION_LOCK_RESULT_SUCCESS);

    EXPECT_TRUE(callback_results.succeeded_);
    EXPECT_FALSE(callback_results.failed_);
}

// Test the following scenario:
// - request1 is received by the dispatcher;
// - request2 is received by the dispatcher;
// - request1 is rejected;
// - request1 success response is received.
// Expected: request1 is still rejected, request2 has not been set as succeeded.
TEST_F(ScreenOrientationDispatcherTest, RaceScenario)
{
    MockLockOrientationCallback::LockOrientationResultHolder callback_results1;
    MockLockOrientationCallback::LockOrientationResultHolder callback_results2;

    LockOrientation(
        blink::WebScreenOrientationLockPortraitPrimary,
        base::MakeUnique<MockLockOrientationCallback>(&callback_results1));
    int request_id1 = GetRequestId();

    LockOrientation(
        blink::WebScreenOrientationLockLandscapePrimary,
        base::MakeUnique<MockLockOrientationCallback>(&callback_results2));

    // callback_results1 must be rejected, tested in CancelPending_DoubleLock.

    RunLockResultCallback(request_id1,
        LockResult::SCREEN_ORIENTATION_LOCK_RESULT_SUCCESS);

    // First request is still rejected.
    EXPECT_FALSE(callback_results1.succeeded_);
    EXPECT_TRUE(callback_results1.failed_);
    EXPECT_EQ(blink::WebLockOrientationErrorCanceled, callback_results1.error_);

    // Second request is still pending.
    EXPECT_FALSE(callback_results2.succeeded_);
    EXPECT_FALSE(callback_results2.failed_);
}

} // namespace content
